;**** A P P L I C A T I O N   N O T E   A V R 3 0 2 ************************
;*
;* Title		: TWI Slave Implementation
;* Version		: 1.2
;* Last updated		: 2002.05.03
;* Target		: AT90Sxxxx (All AVR Device)
;*			  Fast Mode: AT90S1200 only
;*
;* Support email	: avr@atmel.com
;*
;* Code Size		: 160 words
;* Low Register Usage	: 0
;* High Register Usage	: 5
;* Interrupt Usage	: External Interrupt0,
;*			  Timer/Counter0 overflow interrupt
;*
;* DESCRIPTION
;* 	This application note shows how to implement an AVR AT90S1200 device
;*	as an TWI slave peripheral. For use with other AT90S AVR devices, other
;*	I/O pins might have to be used, and the stack pointer might have to be
;*	initialized.
;*	Received data is stored in r0 and r0 is transmitted during a master
;*	read transfer. This simple protocol is implemented for demonstration
;*	purposes only.
;*
;*	Some features :
;*	* Interrupt based (using INT0 and TIM0).
;*	* The device can be given any 7-bit address. (Expandable to 10bit).
;*	* Supports normal and fast mode.
;*	* Easy insertion of "wait states".
;*	* Size and speed optimized.
;*	* Supports wake-up from idle mode.
;*
;*	Refers to the application note AVR302 documentation for more
;*	detailed description.
;*
;* USAGE
;*	Insert user code in the two locations marked "insert user code here".
;*
;* NOTES
;*	Minimum one instruction will be executed between each interrupt.
;*
;* STATISTICS
;*	Code Size	: 160
;*	Register Usage	: 5 High, 0 Low
;*	Interrupt Usage	: EXT_INT0 and TIM0_OVF
;*	Port Usage	: PD4(T0) and PD2(INT0)
;*	XTAL		: - TWI Fast Mode     : min 16.0MHz
;*			  - TWI Standard Mode : min  3.0MHz
;*
;***************************************************************************

;**** Includes ****

.include "avr2313.inc"

;**** Global TWI Constants ****

.equ	devadr	= 0x50		; Slave device address
.equ	devadrm	= 0b01111100	; Slave multi address mask  (accepts from 0x50 until 0x53 )

.equ	pinmask = 0x14		; <=> (1<<PD4)+(1<<PD2)

.equ    portdmask = 0b11101011

;**** Global Register Variables ****

.def	KS108_ctrl	= r14	; KS108_ctrl
.def	eeprom_addr	= r15	; Temporary variable
.def	temp	= r16		; Temporary variable
.def	etemp	= r17		; Extra temporary variable
.def	TWIdata	= r18		; TWI data register
.def	TWIadr	= r19		; TWI address and direction register
.def	TWIstat	= r20		; TWI temporary SREG storage

;**** Interrupt Vectors ****

	rjmp	RESET		; Reset handle
	rjmp	EXT_INT0	; INT0 handle
	rjmp	TIM0_OVF	; Timer 0 overflow handle
;	( rjmp	ANA_COMP )	; ( Analog comparator handle )


;***************************************************************************
;*
;* INTERRUPT
;*	EXT_INT0 / TWI_wakeup
;*
;* DESCRIPTION
;*	Detects the start condition and handles the data transfer on 
;*	the TWI bus.
;*
;*	Received data is stored in the r0 register and r0 is transmitted 
;*	during a master read transfer.
;*
;* NOTE
;*	This interrupt code, in some cases, jumps to code parts in the 
;*	TIM0_OVF / TWI_skip section to reduce code size.
;*
;***************************************************************************

EXT_INT0:			; INT0 handle
	in	TWIstat,SREG	; store SREG


;*************************
;* Get TWI Slave Address *
;*************************

TWI_get_adr:
	ldi	TWIadr,1	; initialize address register
wlo_ga0:sbic	PIND,PIND4	; 	wait for SCL low
	rjmp	wlo_ga0
	rjmp	first_ga
do_ga:				; do
wlo_ga:	sbic	PIND,PIND4	; 	wait for SCL low
	rjmp	wlo_ga

	mov	temp,TWIadr	;	test address
	andi	temp,devadrm	; 	mask "register full" floating bit
	cpi	temp,devadr	; 	if device addressed
	in	temp,SREG	;		set T flag ("T = Z")
	bst	temp,1

first_ga:
	sec			; 	set carry
whi_ga:	sbis	PIND,PIND4	; 	wait for SCL high
	rjmp	whi_ga
	sbis	PIND,PIND2	; 	if SDA low
	clc			;		clear carry
	rol	TWIadr		; 	shift carry into address register

	brcc	do_ga		; while register not full

wlo_ca:	sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_ca

;**** Check Address ****
				; if T flag set (device addressed)
	brts	TWI_adr_ack	;	acknowledge address
				; else
	rjmp	TWI_adr_miss	; 	goto address miss handle

;**** Acknowledge Address ****

TWI_adr_ack:
	sbi	DDRD,DDD2	; assert acknowledge on SDA
whi_aa:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_aa

;**** Check Transfer Direction ****

	lsr	TWIadr		; if master write
	brcs	TWI_master_read;	goto master write handle	
	rjmp	TWI_master_write


;*******************************
;* Transmit Data (master read) *
;*******************************

TWI_master_read:

;**** Load Data (handle outgoing data) ****

;! INSERT USER CODE HERE !
;!NOTE!	If the user code is more than ONE instruction then wait
;	states MUST be inserted as shown in description.

	sbi	DDRD,DDD4	; (start wait state)

	mov	temp,TWIadr	;	test address
	andi	temp,3		; 	last two bits

read_device_x0:
	cpi	temp,0		; 	if device addressed
	brne	read_device_x1

	ldi	temp,0x00
	out	DDRB,temp

        in	TWIdata, PINB

	rjmp	read_device_no

read_device_x1:
	cpi	temp,1		; 	if device addressed
	brne	read_device_x3

	in	temp, DDRD	;	protect scl & sda
	andi    temp, 0b00010100
	out	DDRD, temp
	nop
        in	TWIdata, PIND	;	zero out scl & sda
	andi    TWIdata, 0b11101011

	rjmp	read_device_no

read_device_x3:
	cpi	temp,3		; 	if device addressed
	brne	read_device_no

	rcall	KS108_read_data

read_device_no:
	cbi	DDRD,DDD4	; (stop wait state)


	sec			; shift MSB out and "floating empty flag" in
	rol	TWIdata

wlo_mr:	sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_mr





;**** Transmitt data ****

	brcc	fb_low_mr	; if current data bit high
	cbi	DDRD,DDD2	;	release SDA
	rjmp	fb_mr		;	goto read loop
fb_low_mr:			; else
	sbi	DDRD,DDD2	;	force SDA
fb_mr:	lsl	TWIdata		; 	if data register not empty

loop_mr:
whi_mr:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_mr
wlo_mr2:sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_mr2

	brcc	b_low_mr	; if current data bit high

	cbi	DDRD,DDD2	;	release SDA
	lsl	TWIdata		; 	if data register not empty
	brne	loop_mr		;		loop
	rjmp	done_mr		;	done
b_low_mr:			; else
	sbi	DDRD,DDD2	;	force SDA
	lsl	TWIdata		; 	if data register not empty
	brne	loop_mr		;		loop

done_mr:
whi_mr2:sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_mr2
wlo_mr3:sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_mr3

	cbi	DDRD,DDD2	; release SDA

;**** Read Acknowledge from Master ****

whi_ra:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_ra

	sec			; read acknowledge
	sbis	PIND,PIND2
	clc
	brcc	TWI_master_read	; if ack transfer (next) data

wlo_ra:	sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_ra

	rjmp	TWI_wait_cond	; goto wait condition (rep. start or stop)


;*******************************
;* Receive Data (master write) *
;*******************************

TWI_master_write:

wlo_mw0:sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_mw0
	cbi	DDRD,DDD2	; remove acknowledge from SDA

whi_mw:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_mw

	in	temp,PIND	; sample SDA (first bit) and SCL
	andi	temp,pinmask	; mask out SDA and SCL
do_mw:				; do
	in	etemp,PIND	;	new sample
	andi	etemp,pinmask	;	mask out SDA and SCL
	cp	etemp,temp
	breq	do_mw		; while no change

	sbrs	etemp,PIND4	; if SCL changed to low
	rjmp	receive_data	;	goto receive data

	sbrs	etemp,PIND2	; if SDA changed to low
	rjmp	TWI_get_adr	;	goto repeated start
				; else
	rjmp	TWI_stop	;	goto transfer stop

receive_data:
	ldi	TWIdata,2	; set TWIdata MSB to zero
	sbrc	temp,PIND2	; if SDA sample is one
	ldi	TWIdata,3	;	set TWIdata MSB to one

do_rd:				; do
wlo_rd:	sbic	PIND,PIND4	;	wait for SCL low
	rjmp	wlo_rd
	sec			; 	set carry
whi_rd:	sbis	PIND,PIND4	;	wait for SCL high
	rjmp	whi_rd

	sbis	PIND,PIND2	; 	if SDA low
	clc			;		clear carry
	rol	TWIdata		; 	shift carry into data register

	brcc	do_rd		; while register not full

;**** Acknowledge Data ****

TWI_dat_ack:
wlo_da:	sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_da
	sbi	DDRD,DDD2	; assert acknowledge on SDA
whi_da:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_da
				; (acknowledge is removed later)

;**** Store Data (handle incoming data) ****

	; insert wait states here if nessesary
	sbi	DDRD,DDD4	; (start wait state)

;! INSERT USER CODE HERE !
;!NOTE!	If the user code is more than TWO instruction then wait
;	states MUST be inserted as shown in description.


	mov	temp,TWIadr	;	test address
	andi	temp,3		; 	last two bits

	; try 0
store_device_x0:
	cpi	temp,0		; 	if device addressed
	brne	store_device_x1

	ldi	temp,0xff
	out	DDRB,temp
        out	PORTB, TWIdata
	rjmp	store_device_no

store_device_x1:
	cpi	temp,1		; 	if device addressed
	brne	store_device_x2

	in	temp, DDRD	;	protect scl & sda
	ori	temp, 0b11101011
	out	DDRD, temp

	mov	temp, TWIdata
	andi	temp, 0b11101011

        in	etemp, PORTD
	andi	etemp, 0b00010100

	or	etemp, temp

        out	PORTD, etemp

	rjmp	store_device_no

store_device_x2:
	cpi	temp,2		; 	if device addressed
	brne	store_device_x3
.ifdef 0

	clr	temp

	cp	TWIdata, temp
	breq	disable_OC

	out	OCR1AH,temp
	out	OCR1AL,TWIdata

	ldi	temp, (1<<COM1A1) + (1<<PWM10)
	out	TCCR1A,temp		; non-inverted and 8-bit PWM
	ldi	temp, (1<<CS10)
	out	TCCR1B,temp		; count at CK	

	rjmp	store_device_no

disable_OC:
	out	TCCR1A,temp		; non-inverted and 8-bit PWM

.else

	mov KS108_ctrl, TWIdata
.endif
	rjmp	store_device_no

store_device_x3:
	cpi	temp,3		; 	if device addressed
	brne	store_device_x4

	rcall	KS108_write

	rjmp	store_device_no

store_device_x4:
	cpi	temp,4		; 	if device addressed
	brne	store_device_no


store_device_no:
	cbi	DDRD,DDD4	; (end wait state)

	rjmp	TWI_master_write; Start on next transfer


;***************************************************************************
;*
;* INTERRUPT
;*	TIM0_OVF / TWI_skip
;*
;* DESCRIPTION
;*	This interrupt handles a "address miss". If the slave device is
;*	not addressed by a master, it will not acknowledge the address.
;*
;*	Instead of waiting for a new start condition in a "busy loop"
;*	the slave set up the counter to count 8 falling edges on SCL and 
;*	returns from the current interrupt. This "skipping" of data
;*	do not occupies any processor time. When 8 egdes are counted, a
;*	timer overflow interrupt handling routine (this one) will check
;*	the next condition that accure. If it's a stop condition
;*	the transfer ends, if it's a repeated start condition a jump
;*	to the TWI_wakeup interrupt will read the new address. If a new
;*	transfer is initiated the "skipping" process is repeated.
;*
;***************************************************************************

TIM0_OVF:			; ( Timer 0 overflow handle )
	in	TWIstat,SREG	; store SREG

TWI_adr_miss:
	
;**** Drop Acknowledge ****

whi_dac:sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_dac
wlo_dac:sbic	PIND,PIND4	; wait for SCL low
	rjmp	wlo_dac

	; disable timer 0 overflow interrupt
	ldi	temp,(0<<TOIE0)
	out	TIMSK,temp
	; enable external interrupt request 0
	ldi	temp,(1<<INT0)
	out	GIMSK,temp


;************************
;* Wait for a Condition *
;************************

TWI_wait_cond:
whi_wc:	sbis	PIND,PIND4	; wait for SCL high
	rjmp	whi_wc

	in	temp,PIND	; sample SDA (first bit) and SCL
	andi	temp,pinmask	; mask out SDA and SCL
do_wc:				; do
	in	etemp,PIND	;	new sample
	andi	etemp,pinmask	;	mask out SDA and SCL
	cp	etemp,temp
	breq	do_wc		; while no change

	sbrs	etemp,PIND4	; if SCL changed to low
	rjmp	TWI_skip_byte	;	goto skip byte

	sbrs	etemp,PIND2	; if SDA changed to low
	rjmp	TWI_get_adr	;	goto repeated start
				; else
				;	goto transfer stop


;*************************
;* Handle Stop Condition *
;*************************

TWI_stop:
	;! Set INT0 to generate an interrupt on low level,
	;! then set INT0 to generate an interrupt on falling edge.
	;! This will clear the EXT_INT0 request flag.
	ldi	temp,(0<<ISC01)+(0<<ISC00)
	out	MCUCR,temp
	ldi	temp,(1<<ISC01)+(0<<ISC00)
	out	MCUCR,temp

	; clear eeprom address
	;clr eeprom_addr

	out	SREG,TWIstat	; restore SREG
	reti			; return from interrupt


;****************
;* Skip byte(s) *
;****************

TWI_skip_byte:
	; set counter initial value
	ldi	temp,-7
	out	TCNT0,temp
	; enable timer 0 overflow interrupt
	ldi	temp,(1<<TOIE0)
	out	TIMSK,temp
	; disable external interrupt request 0
	ldi	temp,(0<<INT0)
	out	GIMSK,temp

	out	SREG,TWIstat	; restore SREG
	reti


;***************************************************************************
;*
;* FUNCTION
;*	TWI_init
;*
;* DESCRIPTION
;*	Initialization of interrupts and port used by the TWI interface
;*	and waits for the first start condition.
;*
;* USAGE
;*	Jump to this code directly after a reset.
;*
;* RETURN
;*	none
;*
;* NOTE
;*	Code size can be reduced by 12 instructions (words) if no transfer
;*	is started on the TWI bus (bus free), before the interrupt
;*	initialization is finished. If this can be ensured, remove the
;*	"Wait for TWI Start Condition" part and replace it with a "sei"
;*	instruction.
;*
;***************************************************************************

RESET:
TWI_init:
	ldi 	temp, low(RAMEND)
	out 	SPL,temp		; Stack Pointer Low Byte

	ldi	temp,0xff
	out	DDRB,temp

;**** clear eeprom address ****
	;clr eeprom_addr


;**** PORT Initialization ****

	; Initialize PD2 (INT0) for open colector operation (SDA in/out)
	ldi	temp,(0<<DDD4)+(0<<DDD2)
	out	DDRD,temp

	; Initialize PD4 (T0) for open colector operation (SCL in/out)
	ldi	temp,(0<<PD4)+(0<<PD2)
	out	PORTD,temp

;**** Interrupt Initialization ****

	; Set INT0 to generate an interrupt on falling edge
	ldi	temp,(1<<ISC01)+(0<<ISC00)
	out	MCUCR,temp

	; Enable INT0
	ldi	temp,(1<<INT0)
	out	GIMSK,temp

	; Set clock to count on falling edge of T0
	ldi	temp,(1<<CS02)+(1<<CS01)+(0<<CS00)
	out	TCCR0,temp


;***************************************************
;* Wait for TWI Start Condition (13 intstructions) *
;***************************************************

do_start_w:			; do
	in	temp,PIND	; 	sample SDA & SCL
	com	temp		;	invert
	andi	temp,pinmask	;	mask SDA and SCL
	brne	do_start_w	; while (!(SDA && SCL))

	in	temp,PIND	; sample SDA & SCL
	andi	temp,pinmask	; mask out SDA and SCL
do_start_w2:			; do
	in	etemp,PIND	;	new sample
	andi	etemp,pinmask	;	mask out SDA and SCL
	cp	etemp,temp
	breq	do_start_w2	; while no change

	sbrc	etemp,PIND2	; if SDA no changed to low
	rjmp	do_start_w	;	repeat

	rcall	TWI_get_adr	; call(!) interrupt handle (New transfer)


;***************************************************************************
;*
;* PROGRAM
;*	main - Test of TWI slave implementation
;*
;***************************************************************************

main:	rjmp	main		; loop forever


; D7 D6 D5 D4 D3 D2 D1 D0 | X 0 0 0 CS2 CS1 RW RS 
KS108_write:
	in	temp, DDRD	;	protect scl & sda
	ori	temp, 0b11101011
	out	DDRD, temp
	
	ser	temp
	out	DDRB, temp

	mov etemp, KS108_ctrl
	mov temp, TWIdata

	bst temp, 0	; D7 D6 D5 D4 D3 D2 D1 D0 | X D1 D0 0 CS2 CS1 RW RS
	bld etemp, 5
	bst temp, 1
	bld etemp, 6

	ror temp	; 0  0  D7 D6 D5 D4 D3 D2 | X D1 D0 0 CS2 CS1 RW RS 
	ror temp
	
	bst etemp, 2	; CS1 CS1 D7 D6 D5 D4 D3 D2 | X D1 D0 0 CS2 CS1 RW RS 
	bld temp, 6
	bst etemp, 3
	bld temp, 7

	andi etemp, 0b01100011  ; CS2 CS1 D7 D6 D5 D4 D3 D2 | X D1 D0 0 0 0 RW RS 
	
	out portb, temp
	out portd, etemp
	cbi portd, 1		;	rw=0

	sbi portd,3
	cbi portd,3
	ret



; CS2 CS1 D7 D6 D5 D4 D3 D2 | X D1 D0 0 0 0 RW RS
KS108_read_data:

	ldi	temp, 0b11000000
	out	DDRB, temp

	clr	temp
	bst	KS108_ctrl, 2	; CS1 CS1 D7 D6 D5 D4 D3 D2 | X D1 D0 0 CS2 CS1 RW RS 
	bld	temp, 6
	bst	KS108_ctrl, 3
	bld	temp, 7

	out	PORTB, temp


	ldi	etemp, 0b10001011    ;
	out	DDRD, etemp
	out	PORTD, etemp
	cbi	PORTD, 3		    ;   E = 0
	nop
	nop	
	nop
	nop
        in      temp, PINB
	rol     temp
	rol     temp
	
	in      etemp, PIND

	bst	etemp, 5
	bld	temp, 0
	bst	etemp, 6
	bld	temp, 1

	mov	TWIdata, temp
	
	ret

;**** End of File ****

